//
// Created by THDMI on 24-3-30.
//

#include "ldss_network_http.h"

#include <thread>

ldss_mq::LDSS_NetHTTP::~LDSS_NetHTTP() {
    this->p_flag_init = false;
    if (this->p_flag_running_server) {
        this->p_flag_running_server = false;
        std::unique_lock _lock(this->p_mtx_server);
        this->p_cov_server.wait(_lock);
    }
    mg_mgr_free(&this->p_client);
    mg_mgr_free(&this->p_server);
}

bool ldss_mq::LDSS_NetHTTP::Init(const std::string &name_, const std::string &option_, std::string &err_) {
    if (this->p_flag_init) {
        err_ = "already initialized, don't repeated";
        return false;
    }
    if (name_.empty()) {
        err_ = "empty name";
        return false;
    }
    this->p_name = name_;
    this->p_flag_init = true;
    return true;
}

bool ldss_mq::LDSS_NetHTTP::ServerRegister(EmMethodHTTP method_, const std::string &router_,
                                           const FuncReqCallback &callback_, std::string &err_) {
    if (router_.empty()) {
        err_ = "empty router register";
        return false;
    }

    if (this->p_flag_running_server) {
        err_ = "server has been running and refuse register";
        return false;
    }

    if (0 != this->p_uom_callback_server.count(router_)) {
        err_ = "router has been registered: " + router_;
        return false;
    }

    switch (method_) {
        case EmMethodHTTP::METHOD_UNDEFINED:
            this->p_uom_callback_server["UNDEFINED"].insert({router_, callback_});
            break;
        case EmMethodHTTP::METHOD_GET:
            this->p_uom_callback_server["GET"].insert({router_, callback_});
            break;
        case EmMethodHTTP::METHOD_POST:
            this->p_uom_callback_server["POST"].insert({router_, callback_});
            break;
        case EmMethodHTTP::METHOD_PUT:
            this->p_uom_callback_server["PUT"].insert({router_, callback_});
            break;
        case EmMethodHTTP::METHOD_DELETE:
            this->p_uom_callback_server["DELETE"].insert({router_, callback_});
            break;
        default:
            err_ = "no select in HTTP method list";
            return false;
    }
    return true;
}

bool ldss_mq::LDSS_NetHTTP::ServerStart(const std::string &addr_, std::uint16_t port_, std::string &err_) {
    if (!this->p_flag_init) {
        err_ = "not initialized the class";
        return false;
    }

    if (this->p_flag_running_server) {
        err_ = "listen already running";
        return false;
    }

    if (addr_.empty()) {
        err_ = "empty address";
        return false;
    }

    this->p_flag_running_server = true;

    mg_mgr_init(&this->p_server);
    if (nullptr == mg_http_listen(&this->p_server, ("http://" + addr_ + ":" + std::to_string(port_)).c_str(),
                                  [](mg_connection *conn_, const int ev_, void *data_) {
                                      if (ev_ == MG_EV_HTTP_MSG) {
                                          // remark: transform stored value pointer from extenal `mg_http_listen`
                                          const auto _uom_callback = static_cast<std::unordered_map<std::string,
                                              std::unordered_map<std::string, FuncReqCallback> > *>(conn_->fn_data);

                                          // remark: get the HTTP message handle
                                          const auto _hm = static_cast<mg_http_message *>(data_);

                                          // remark: find the URI from request headers
                                          auto _find = 0;
                                          for (; _find < _hm->uri.len; ++_find) {
                                              if (_hm->uri.ptr[_find] == ' ') {
                                                  break;
                                              }
                                          }
                                          const auto _router = std::string(&_hm->uri.ptr[0], _find);

                                          // remark: judge HTTP method request and wheher register callback function
                                          auto _method = EmMethodHTTP::METHOD_UNDEFINED;
                                          std::uint64_t _count_find = 0;
                                          if (0 == mg_vcmp(&_hm->method, "UNDEFINED")) {
                                              _count_find = _uom_callback->at("UNDEFINED").count(_router);
                                          } else if (0 == mg_vcmp(&_hm->method, "GET")) {
                                              _method = EmMethodHTTP::METHOD_GET;
                                              _count_find = _uom_callback->at("GET").count(_router);
                                          } else if (0 == mg_vcmp(&_hm->method, "POST")) {
                                              _method = EmMethodHTTP::METHOD_POST;
                                              _count_find = _uom_callback->at("POST").count(_router);
                                          } else if (0 == mg_vcmp(&_hm->method, "PUT")) {
                                              _method = EmMethodHTTP::METHOD_PUT;
                                              _count_find = _uom_callback->at("PUT").count(_router);
                                          } else if (0 == mg_vcmp(&_hm->method, "DELETE")) {
                                              _method = EmMethodHTTP::METHOD_DELETE;
                                              _count_find = _uom_callback->at("DELETE").count(_router);
                                          }

                                          // remark: return when not found the callback function
                                          if (0 == _count_find) {
                                              return;
                                          }

                                          // remark: request message get
                                          StructReqMessage _req{};

                                          // remark: response message set
                                          StructRepMessage _rep{};

                                          // remark: dispose connection option
                                          StructRepOption _option{};

                                          try {
                                              switch (_method) {
                                                  case EmMethodHTTP::METHOD_UNDEFINED:
                                                      _uom_callback->at("UNDEFINED").at(_router)(_req, _rep, _option);
                                                      break;
                                                  case EmMethodHTTP::METHOD_GET:
                                                      _uom_callback->at("GET").at(_router)(_req, _rep, _option);
                                                      break;
                                                  case EmMethodHTTP::METHOD_POST:
                                                      _uom_callback->at("POST").at(_router)(_req, _rep, _option);
                                                      break;
                                                  case EmMethodHTTP::METHOD_PUT:
                                                      _uom_callback->at("PUT").at(_router)(_req, _rep, _option);
                                                      break;
                                                  case EmMethodHTTP::METHOD_DELETE:
                                                      _uom_callback->at("DELETE").at(_router)(_req, _rep, _option);
                                                      break;
                                                  default: ;
                                              }
                                              mg_http_reply(conn_, 200, "", "{%m:%lu}\n", MG_ESC("time"),
                                                            time(nullptr));
                                          } catch (const std::exception &exception) {
                                              mg_http_reply(conn_, 500, "", "{%m:%lu, \"err\":\"%s\"}\n",
                                                            MG_ESC("time"), time(nullptr), exception.what());
                                          }
                                      } else if (ev_ == MG_EV_WS_MSG) {
                                      } else if (ev_ == MG_EV_MQTT_MSG) {
                                      }
                                  }, &this->p_uom_callback_server)) {
        err_ = "cannot create listener";
        mg_mgr_free(&this->p_server);
        return false;
    }

    auto _td = std::thread([this] {
        while (this->p_flag_running_server) {
            mg_mgr_poll(&this->p_server, 1000);
        }
        this->p_cov_server.notify_one();
    });
    _td.detach();

    return true;
}

void ldss_mq::LDSS_NetHTTP::ServerStop() {
    this->p_flag_running_server = false;
}

bool ldss_mq::LDSS_NetHTTP::Send(const std::string_view &addr_, std::uint16_t const port_,
                                 EmMethodHTTP method_, const std::string_view &router_,
                                 const std::vector<char> &data_, const FuncRepCallback &func_) {
    if (!this->p_flag_init) {
        return false;
    }
    mg_mgr_init(&this->p_client);

    // remark: flag which whether server reply
    std::atomic_bool _flag_reply{};

    // remark: request URL
    std::string_view _url = std::string("http://") + addr_.data() + ":" + std::to_string(port_);

    mg_http_connect(&this->p_client, _url.data(), [](mg_connection *conn_, const int ev_, void *data_) {
        if (MG_EV_CONNECT == ev_) {
            // TODO send message
        } else if (MG_EV_HTTP_MSG) {
            // TODO response receive
            const auto _hm = static_cast<mg_http_message *>(data_);
        }
    }, &_flag_reply);
    while (!_flag_reply) {
        mg_mgr_poll(&this->p_client, 1000);
    }
    mg_mgr_free(&this->p_client);
    return true;
}
